/**
 ** This file is part of FK Klistret Extensions. FK Klistret Extensions 
 ** is free software: you can redistribute it and/or modify it under the 
 ** terms of the GNU General Public License as published by the Free 
 ** Software Foundation, either version 3 of the License, or (at your option) 
 ** any later version.

 ** FK Klistret Extensions is distributed in the hope that it will be 
 ** useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 ** General Public License for more details. You should have received a
 ** copy of the GNU General Public License along with FK Klistret Extensions. 
 ** If not, see <http://www.gnu.org/licenses/>
 */
package se.fk.klistret.console.jaxb;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Set;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.jboss.resteasy.plugins.providers.jaxb.JaxbCollection;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import se.fk.klistret.console.exceptions.InfrastructureException;

/**
 * 2011-07-11, The comments below are from 2005 and the code is has since
 * upgraded to the Ant 1.8 platform. The patterns and design of this project
 * should not be reused since they are not considered of any quality. The code
 * was written quickly without long term concerns.
 * 
 * @author Matthew Young
 * 
 */
public class CIContext {

	private static final Logger logger = LoggerFactory
			.getLogger(CIContext.class);

	private volatile static CIContext ciContext;

	private JAXBContext jaxbContext;

	private Set<Class<?>> contextPath;

	private Set<Class<? extends com.klistret.cmdb.ci.commons.Element>> elements;

	private Set<Class<? extends com.klistret.cmdb.ci.commons.Relation>> relations;

	private Set<Class<?>> proxies;

	/**
	 * Singleton constructor
	 */
	private CIContext() {
		/**
		 * Eliminate invalid URLs (tested the Commons Validate suite but the
		 * file schema was not supported)
		 */
		Set<URL> validations = pruneURLs(ClasspathHelper
				.getUrlsForCurrentClasspath());

		/**
		 * Using scannotation model to find classes with particular annotations
		 * (noteworthly if the SubTypesScanner isn't included then the Inherited
		 * annotation is not utilized)
		 */
		Reflections reflections = getReflections(validations);

		/**
		 * JAXB context path
		 */
		contextPath = new HashSet<Class<?>>();

		/**
		 * Find all of the CI elements
		 */
		elements = reflections
				.getSubTypesOf(com.klistret.cmdb.ci.commons.Element.class);
		for (Class<?> element : elements) {
			if (contextPath.add(element))
				logger.debug("Adding element {} to JAXB context path",
						element.getName());
		}

		/**
		 * Find all of the CI relations
		 */
		relations = reflections
				.getSubTypesOf(com.klistret.cmdb.ci.commons.Relation.class);
		for (Class<?> relation : relations) {
			if (contextPath.add(relation))
				logger.debug("Adding relation {} to JAXB context path",
						relation.getName());
		}

		/**
		 * Add all of the proxies
		 */
		proxies = new HashSet<Class<?>>();
		proxies.add(com.klistret.cmdb.ci.pojo.Element.class);
		proxies.add(com.klistret.cmdb.ci.pojo.ElementType.class);
		proxies.add(com.klistret.cmdb.ci.pojo.Relation.class);
		proxies.add(com.klistret.cmdb.ci.pojo.RelationType.class);

		for (Class<?> proxy : proxies)
			if (contextPath.add(proxy))
				logger.debug("Adding proxy {} to JAXB context path",
						proxy.getName());

		/**
		 * Handle JAXB collections
		 */
		contextPath.add(JaxbCollection.class);

		/**
		 * Construct the JAXContext
		 */
		try {
			jaxbContext = JAXBContext.newInstance(contextPath
					.toArray(new Class[0]));
			logger.debug("Created JAXB context");
		} catch (JAXBException e) {
			throw new InfrastructureException("Unable to create JAXBContext", e);
		}
	}
	
	/**
	 * Eliminate invalid URL candidates from the URL result set (otherwise
	 * unnecessary exceptions are thrown in the container)
	 * 
	 * @param candidates
	 * @return
	 */
	private Set<URL> pruneURLs(Set<URL> candidates) {
		/**
		 * System property filter for publications files
		 */
		String[] publications = System.getProperty("ci.publications") == null ? null
				: System.getProperty("ci.publications").split(",", 0);

		Set<URL> validations = new HashSet<URL>();

		for (URL candidate : candidates) {
			try {
				URLConnection connection = candidate.openConnection();
				connection.connect();

				if (connection instanceof HttpURLConnection) {
					HttpURLConnection httpConnection = (HttpURLConnection) connection;
					int code = httpConnection.getResponseCode();

					if (code == HttpURLConnection.HTTP_OK)
						validations.add(candidate);
					else
						logger.warn(
								"URL [{}] connection response code [{}] elimated from valid URLs for Scannoation",
								candidate.toString(), code);
				} else {
					if (connection.getContentLength() > 0)
						validations.add(candidate);
					else
						logger.warn(
								"URL [{}] content length [{}] elimated from valid URLs for Scannoation",
								candidate.toString(),
								connection.getContentLength());
				}
			} catch (IOException e) {
				logger.warn(
						"URL [{}] connect failed [{}] elimated from valid URLs for Scannoation",
						candidate.toString(), e.getMessage());
			}
		}
		logger.warn("{} URLs elimated from {} candidates", candidates.size()
				- validations.size(), candidates.size());

		/**
		 * Reduce the results to only jars listed in the system property
		 */
		if (publications != null) {
			Set<URL> others = new HashSet<URL>(publications.length);
			for (URL url : validations)
				for (String name : publications)
					if (url.getPath().contains(name)) {
						others.add(url);
						logger.debug("Reduced candidate to pool with match {}",
								url.getPath());
					}

			if (others.size() != 0)
				return others;
		}

		return validations;
	}
	
	/**
	 * Separate method to allow for monitoring
	 * 
	 * @param validations
	 * @return
	 */
	private Reflections getReflections(Set<URL> validations) {
		return new Reflections(new ConfigurationBuilder().setUrls(validations)
				.setScanners(new TypeAnnotationsScanner(),
						new SubTypesScanner(), new ResourcesScanner()));
	}

	/**
	 * Forces a singleton instance of this object
	 * 
	 * @return
	 */
	public static CIContext getCIContext() {
		if (ciContext == null) {
			synchronized (CIContext.class) {
				if (ciContext == null)
					ciContext = new CIContext();
			}
		}
		return ciContext;
	}

	public JAXBContext getJAXBContext() {
		return jaxbContext;
	}
}
